package cn.yunbamboos.token;

import cn.hutool.crypto.digest.DigestUtil;
import cn.yunbamboos.Const;
import cn.yunbamboos.exception.AppException;
import cn.yunbamboos.token.deserializer.TokenDeserializer;
import cn.yunbamboos.token.serializer.TokenSerializer;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.function.BiConsumer;

/**
 * 默认的token实现
 */
public class Token implements IToken {

    private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;
    private final static String SUBJECT = "Peripherals";

    /**
     * ID
     */
    String id;
    /**
     * 客户端ip
     */
    String ip;
    /**
     * 客户端
     */
    String browser;
    /**
     * 动态属性
     */
    JSONObject attrs;
    /**
     * 证书
     */
    String secret;
    /**
     * 签名
     */
    String issuer;
    /**
     * 当前token创建时间
     */
    long createTime;
    /**
     * 失效时间(单位毫秒)
     */
    long expireTime;

    Token() {
    }

    @Override
    public String toID() {
        return DigestUtil.md5Hex(id + "-" + ip + "-" + browser);
    }

    @Override
    public void set(String key, Object value) {
        this.attrs.put(key, value);
    }

    @Override
    public String getString(String key) {
        return this.attrs.getString(key);
    }

    @Override
    public void forEach(BiConsumer<String, Object> action){
        this.attrs.forEach(action);
    }

    @Override
    public int getInteger(String key) {
        return this.attrs.getInteger(key);
    }

    @Override
    public JSONObject getJSONObject(String key) {
        return this.attrs.getJSONObject(key);
    }

    @Override
    public JSONArray getJSONArray(String key) {
        return this.attrs.getJSONArray(key);
    }

    // 由字符串生成加密key
    private SecretKey generalKey() {
        // 本地的密码解码
        byte[] encodedKey = Base64.getEncoder().encode(Const.TOKEN_SECRET.getBytes());
        // 根据给定的字节数组使用AES加密算法构造一个密钥
        return new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    }

    /**
     * 创建token
     */
    public String create() {
        JSONObject json = new JSONObject();
        json.put("id", id);
        json.put("ip", ip);
        json.put("browser", browser);
        json.put("attrs", attrs);

        SecretKey key = Keys.hmacShaKeyFor(DigestUtil.md5Hex(secret).getBytes());

        return Jwts.builder()
                // 设置头部信息header
                .header()
                .add("typ", "JWT")
                .add("alg", "HS256")
                .and()
                // 自定义信息
                .claim("json", json.toJSONString())
                // 令牌ID
                .id(id)
                // 过期时间
                .expiration(new Date(createTime + expireTime))
                // 签发时间
                .issuedAt(new Date(createTime))
                // 主题
                .subject(SUBJECT)
                // 签发者
                .issuer(issuer)
                // 签名
                .signWith(key, ALGORITHM)
                .json(new TokenSerializer<>())
                .compact();
    }

    /**
     * 解密Token
     */
    public void parse(String tokenStr) {
        try {
            SecretKey key = Keys.hmacShaKeyFor(DigestUtil.md5Hex(secret).getBytes());

            Jws<Claims> jws = Jwts.parser()
                    .verifyWith(key).json(new TokenDeserializer())
                    .build()
                    .parseSignedClaims(tokenStr);
            Claims claims = jws.getPayload();

            String jsonStr = claims.get("json", String.class);
            JSONObject json = JSON.parseObject(jsonStr);
            this.id = json.getString("id");
            this.ip = json.getString("ip");
            this.browser = json.getString("browser");
            this.attrs = json.getJSONObject("attrs");

        } catch (Exception e) {
            throw AppException.c(401, "解析token异常");
        }
    }

}
